AWSでホストされているアプリケーションをSalesforceから署名付きPOSTで呼び出してみた(Canvas使用)
AWSなどSalesforceの外部でホストされているアプリケーションをSalesforceから呼び出す方法の一つにCanvasがあります。
Canvas 開発者ガイド
https://developer.salesforce.com/docs/atlas.ja-jp.240.0.platform_connect.meta/platform_connect/canvas_framework_intro.htm
Canvasは署名付きPOST ( Signed Request ) に対応しており、これを使うことで、 Salesforceで認証されたユーザーに対してのみ、目的のアプリケーションに透過的にアクセスさせることが可能です。本エントリーでは、Canvasを使ってAWSでホストされているアプリケーション(以後、Canvasアプリケーションと記述します)にアクセスし、Salesforceのユーザ情報をアプリケーション側で取り出す方法について書きました。
なお、Canvasを扱うためのSDKが提供されています。
- https://github.com/forcedotcom/SalesforceCanvasFrameworkSDK
- https://github.com/forcedotcom/SalesforceCanvasJavascriptSDK
しかし、これらはメンテナンスが数年前から動いていないようで、アーキテクチャも古いため、前出の「Canvas 開発者ガイド」に従って、自前で署名付きPOSTを解析する方法を取りました。
Salesforce内部から外部アプリケーションを安全に呼び出したい方の参考になれば嬉しいです。
SalesforceでCanvasを定義する
Canvasを使用するためには、SalesforceにてCanvas用の接続アプリケーションを定義する必要があります。
接続アプリケーションをCanvas向けに定義する
Salesforceの[設定] > [アプリケーションマネージャ]から[新規接続アプリケーション]をクリックして、Canvas用の接続アプリケーションを作成します。
接続アプリケーションの主な設定は次の通りです。
項目 | 設定値 |
---|---|
接続アプリケーション名 | 任意の値 |
API 参照名 | 任意の値(ここではMY_CANVASとしました) |
OAuth 設定の有効化 | チェックを入れる |
コールバック URL | 適当なURL(ここではCanvasアプリケーションのURLと同じドメインの/callbackパスとしました) |
選択した OAuth 範囲 | 「APIを使用してユーザデータを管理(api)」を選択 |
Web サーバフローの秘密が必要 | チェックを入れる(デフォルトのまま) |
更新トークンフローの秘密が必要 | チェックを入れる(デフォルトのまま) |
キャンバス | チェックを入れる |
キャンバスアプリケーションの URL | CanvasアプリケーションのURL(POST先のURL) |
アクセス方法 | 「署名付き要求(POST)」を選択 |
場所 | 「Visualforce ページ」を選択 |
Canvasアプリケーションを埋め込む場所にVisualforceを選択しましたが、その他の場所も指定できます。
Visualforceから定義した接続アプリケーションを呼び出す
Visualforceから呼び出すためには、定義した接続アプリケーションのAPI参照名を使って次のようにします。
<apex:page> <apex:canvasApp developerName="MY_CANVAS" height="1000px" width="800px" parameters="{p1:'value1',p2:'value2',p3:'value3'}"/> </apex:page>
developerNameにAPI参照名を渡します。
また、parametersにJSON形式でCanvasアプリケーションに渡すパラメータを指定することができます。
外部でホストされるアプリケーション(Canvasアプリケーション)を用意する
POST先のCanvasアプリケーションを用意します。ここでは簡単にAWS Amplifyで用意することにしました。
AWS Amplifyで署名付きPOST先のCanvasアプリケーションを用意する
Canvasアプリケーションのアプリ名をsample-canvas
としました。ここではReactを使っています。
$ npx create-react-app sample-canvas $ cd sample-canvas
Amplifyプロジェクトを立ち上げて初期化します。
$ amplify init ? Enter a name for the environment dev ? Choose your default editor: Vim (via Terminal, macOS only) ? Choose the type of app that you're building javascript Please tell us about your project ? What javascript framework are you using react ? Source Directory Path: src ? Distribution Directory Path: build ? Build Command: npm run-script build ? Start Command: npm run-script start Using default provider awscloudformation ? Select the authentication method you want to use: AWS profile ? Please choose the profile you want to use [select your profile]
Lambda functionを追加します。関数名はhello
としました。
$ amplify add function ? Select which capability you want to add: Lambda function (serverless function) ? Provide an AWS Lambda function name: hello ? Choose the runtime that you want to use: NodeJS ? Choose the function template that you want to use: Hello World Available advanced settings: - Resource access permissions - Scheduled recurring invocation - Lambda layers configuration - Environment variables configuration - Secret values configuration ? Do you want to configure advanced settings? No ? Do you want to edit the local lambda function now? No
定義したLambda functionにアクセスするAPI Gatewayを追加します。API名もhello
としました。
$ amplify add api ? Select from one of the below mentioned services: REST ✔ Would you like to add a new path to an existing REST API: (y/N) · no ✔ Provide a friendly name for your resource to be used as a label for this category in the project: · hello ✔ Provide a path (e.g., /book/{isbn}): · /hello ✔ Choose a Lambda source · Use a Lambda function already added in the current Amplify project ✔ Choose the Lambda function to invoke by this path · hello ✔ Restrict API access? (Y/n) · no ✔ Do you want to add another path? (y/N) · no
Restrict API access?
をnoにしていますが、本番運用する場合はYesにしてください。
定義したリソースをデプロイします。
$ amplify publish
https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/helloのようなAPI GatewayのURLが生成されます。
生成されたAPI GatewayのURLはSalesforceのCanvasアプリケーション設定のキャンバスアプリケーションの URL
に忘れずに設定してください。
署名付きPOSTをLambdaで解析して、Salesforceユーザー情報を取り出す
ここまでで、作成したVisualforceページにアクセスすると、CanvasによってAmplifyで作成したCanvasアプリケーションが認証付きPOSTでコールされるようになっています。あとは、Canvasアプリケーション側で認証付きPOSTを解析して、Salesforceから渡される各種情報を取り出します。
具体的にはAmplifyで作成したLambda function(amplify/backend/function/hello/src/index.js
)を次のように書き換えます。
import crypto from 'crypto'; function decode(signed_request, secret) { if( !signed_request || !secret ) { throw new Error('Must pass both signed_request and api secret'); } // decode the data let data, encoded_data, sig; try { encoded_data = signed_request.split('.', 2); sig = encoded_data[0]; const json = Buffer.from(encoded_data[1], 'base64').toString(); data = JSON.parse(json); } catch ( e ) { throw new Error('Could not parse signed-request'); } // check algorithm if ( !data.algorithm || data.algorithm.toUpperCase() !== 'HMACSHA256' ) { throw new Error('Unknown algorithm. Expected HMACSHA256'); } // check sign const expected_sig = crypto.createHmac('sha256', secret).update(encoded_data[1]).digest('base64'); if ( sig !== expected_sig ) { throw new Error('Bad signed JSON Signature!'); } return data; } export async function handler(event) { try { const signed_req = decodeURIComponent(event.body.split('=', 2)[1]); const body = decode(signed_req, '<接続アプリケーションの[コンシューマの秘密]を指定する>'); return { statusCode: 200, body: JSON.stringify(body.context.user) }; } catch ( e ) { console.error(e); } };
POSTのbodyにsigned_request=署名付きPOSTの値
というフォーマットで署名付きPOSTの値が送られてくるので、これを取り出して、署名付き要求の検証および復号化のドキュメントにならって、リクエストの検証と復号化をdecode関数で行なっています。
最初にdecodeURIComponent
でリクエストをURIデコードする必要があることに留意してください(これでめちゃくちゃ嵌りました)。
最後に、body.context.user
でSalesforceのユーザ情報を取り出して返しています。そのほかの情報も取り出せますので、詳しくは次のリファレンスを参照してください。
ユーザ情報にはロールID(roleId)なども含まれているので、ユーザのロールによってCanvasアプリケーション側の挙動を変える、といったことも可能です。
署名付きPOSTを検証することで、確実にCanvasを定義したSalesforce組織からのリクエストであることを保証できています。 作成したVisualforceページにアクセスして、Salesforceのユーザ情報(今、ログインしている自身のSalesforceのユーザ情報)がJSONで返ってきていれば成功です。
まとめ
SalesforceのCanvasを用いて、外部でホストされているアプケーションをSalesforce内部から安全に呼び出す方法について書きました。署名付き要求の取り扱い方の参考になれば幸いです。
Canvasを活用することで、Salesforceの認証を引き継ぎつつ、複雑な画面機能はAWSなどのSalesforce外部の仕組みを使って実装することが可能になります。アプリケーションの表現力が強力になりますので、検討してみてください。